package org.hs.phd.common.simpleplayer.player;

import java.awt.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.PriorityBlockingQueue;

import org.hs.phd.common.simpleplayer.FramViewerController;
import org.hs.phd.common.simpleplayer.FramePlayer;
import org.hs.phd.common.simpleplayer.FrameSource;
import org.hs.phd.common.simpleplayer.ImageSequenceFrame;
import org.hs.phd.common.simpleplayer.PlayerEvent;
import org.hs.phd.common.simpleplayer.PlayerEvent.EventType;
import org.hs.phd.common.simpleplayer.PlayerFilter;
import org.hs.phd.common.simpleplayer.PlayerListener;
import org.hs.phd.common.simpleplayer.PlayerViewerLayer;
import org.hs.phd.common.simpleplayer.frameviewer.DefaultFrameViewerImpl;
import org.hs.phd.common.simpleplayer.frameviewer.FrameViewer;

public class DefaultFramePlayer implements FramePlayer, Runnable {

    private static final int EVENT_QUEUE_CAPACITY = 100;

	private enum State {PLAYING, PAUSED, STOPPED}

    private final FrameSource frameSource;
    private volatile State state;
    private Thread playerThread;
    private final int interFrameMilliseconds;
	private List<PlayerFilter> filters;
	private FrameViewer frameViewer;
	private PriorityBlockingQueue<PlayerEvent> eventQueue;
	
	private PlayerEvent PLAY_EVENT = new PlayerEvent(EventType.PLAY);
	private PlayerEvent PAUSE_EVENT = new PlayerEvent(EventType.PAUSE);
	private PlayerEvent RESET_EVENT = new PlayerEvent(EventType.RESET);
	private PlayerEvent PLAY_NEXT_EVENT = new PlayerEvent(EventType.PLAY_NEXT);
	private PlayerEvent STEP_NEXT_EVENT = new PlayerEvent(EventType.STEP_NEXT);
	private PlayerEvent STEP_PREVIOUS_EVENT = new PlayerEvent(EventType.STEP_PREVIOUS);
	private PlayerEvent STOP_EVENT = new PlayerEvent(EventType.STOP);
	private PlayerEvent VIEWER_REFRESH_EVENT = new PlayerEvent(EventType.VIEWER_REFRESH);
		
	private List<PlayerListener> listeners;
	private ImageSequenceFrame currentFrame;

	
	
    public DefaultFramePlayer(FrameSource frameSource ) {
        this.frameSource = frameSource;
        this.interFrameMilliseconds = (int) (1000/frameSource.getFPS());

        filters = new ArrayList<PlayerFilter>();
        eventQueue = new PriorityBlockingQueue<PlayerEvent>(EVENT_QUEUE_CAPACITY, new Comparator<PlayerEvent>() {

			@Override
			public int compare(PlayerEvent evt1, PlayerEvent evt2) {
				int evt1Value = evt1.getEventType() == EventType.PLAY_NEXT ? 1 : 0;
				int evt2Value = evt2.getEventType() == EventType.PLAY_NEXT ? 1 : 0;
				return evt1Value - evt2Value;
			}
		});
        
        listeners = Collections.synchronizedList(new ArrayList<PlayerListener>());
        
        state = State.PAUSED;
        playerThread = new Thread(this);
        playerThread.start();

        frameViewer = new DefaultFrameViewerImpl(this);
    }

    @Override
    public void play() {
		fireEvent(PLAY_EVENT);
    }

    @Override
    public void pause() {
    	fireEvent(PAUSE_EVENT);
    }

    @Override
    public void reset() {
    	fireEvent(RESET_EVENT);
    }

    @Override
    public void stop() {
    	fireEvent(STOP_EVENT);
    }

    @Override
    public void refreshViewer() {
    	fireEvent(VIEWER_REFRESH_EVENT);
    }

    @Override
    public void stepNext() {
    	fireEvent(STEP_NEXT_EVENT);
    }

    @Override
    public void stepPrevious() {
    	fireEvent(STEP_PREVIOUS_EVENT);
    }

    @Override
    public boolean isPaused() {
        return state == State.PAUSED;
    }

    @Override
    public void run() {
        while (true) {
            
        	PlayerEvent event = null;

        	try {
				event = eventQueue.take();
			} catch (InterruptedException e) {
				continue;
			}
        	
        	event.setPlayer(this);
			
        	processEvent(event);
			dispatchEvent(event);
			
        	if( state == State.STOPPED ){
        		dispose();
        		return;
        	}
        	
        	
        }
    }

	private void dispose() {
		eventQueue.clear();
		listeners.clear();
		if(currentFrame != null){
			currentFrame.dispose();
		}
	}

	private void processEvent(PlayerEvent event){
		switch (event.getEventType()) {
		case PLAY_NEXT:
			if(state == State.PLAYING){
				processNextFrame();
			}
			break;
		case STEP_NEXT:
			processNextFrame();
			break;
		case STEP_PREVIOUS:
			processPreviousFrame();
			break;
		case PAUSE:
			state = State.PAUSED;
			break;
		case PLAY:
			state = State.PLAYING;
    		eventQueue.put(PLAY_NEXT_EVENT);
			break;
		case RESET:
			state = State.PAUSED;
			frameSource.reset();
			break;
		case STOP:
			state = State.STOPPED;
			break;
		case VIEWER_REFRESH:
			pushCurrentFrameToViewer();
			break;
		case CUSTOM_EVENT:
			//do nothing
			break;
		default:
			throw new Error("Not a known event type!");
		}
		
	}

    private void pushCurrentFrameToViewer() {
		if( frameViewer != null &&  currentFrame != null ){
			frameViewer.show(currentFrame);
		}
	}

	private void dispatchEvent(PlayerEvent event) {
		for(PlayerListener listener : listeners){
			listener.playerEventOccurred(event);			
    	}
	}

	public boolean isFinished() {
        return state == State.STOPPED;
    }

    private void processNextFrame() {
        ImageSequenceFrame frame = frameSource.loadNextFrame();

        processFrame(frame);

        if( state == State.PLAYING){
    		eventQueue.put(PLAY_NEXT_EVENT);
    	}
    }

    private void processPreviousFrame() {
        ImageSequenceFrame frame = frameSource.loadPreviousFrame();

        processFrame(frame);
    }

    private void processFrame(ImageSequenceFrame frame) {
        if (frame != null) {
            try {
            	
            	if(currentFrame != null){
            		currentFrame.dispose();
            	}
            	
            	currentFrame = frame;
            	
        		for(PlayerFilter filter : filters){
					filter.filter(frame);
            	}
            	
        		pushCurrentFrameToViewer();
            	
                Thread.sleep(interFrameMilliseconds);
            } catch (InterruptedException e) {
                // ignore
            }
        }

    }

	@Override
	public void addFilter(PlayerFilter filter) {
		filters.add(filter);
	}

	@Override
	public void addViewerLayer(PlayerViewerLayer layer) {
		frameViewer.addViewerLayer(layer);
	}

	@Override
	public void disableViewer() {
		frameViewer = null;
	}

	@Override
	public Component getViewerComponent() {
		return frameViewer.getUI();
	}

	@Override
	public FramViewerController getViewerController() {
		return frameViewer.getViewerController();
	}

	@Override
	public void fireEvent(PlayerEvent event) {
    	if( state == State.STOPPED ) return;

		eventQueue.put(event);
	}

	@Override
	public void addEventListener(PlayerListener listener) {
    	if( state == State.STOPPED ) return;

    	listeners.add(listener);
	}

	@Override
	public ImageSequenceFrame getCurrentFrame() {
		return currentFrame;
	}
}
